// Burn - Drivers module

#include "version.h"
#include "burnint.h"
#include "burn_sound.h"
#include "driverlist.h"
#include "cheat.h"

BurnInterface burnInterface;

// filler function, used if the application is not printing debug messages
static int __cdecl BurnbprintfFiller(int, const char* , ...) { return 0; }
// pointer to burner printing function
int (__cdecl *bprintf)(int status, const char* format, ...) = BurnbprintfFiller;

// reinitialise gui screen
void (__cdecl *BurnReinitScrn)() = NULL;

int burnVersion = BURN_VERSION;			// Version number of the library

// FBA Combo - extra players input hack
bool inputSwap = false;
unsigned char kDropped = 0xFF;
void (*BurnDrvInputSwap)() = NULL;

#if defined (FBA_DEBUG)
static clock_t starttime = 0;			// system time when emulation started and after roms loaded
#endif

static unsigned burnDrvCount;			// Count of game drivers
static unsigned burnDrvSelect;			// Which game driver is selected

static unsigned currentFrame;			// Framecount for emulated game
static unsigned framesEmulated;			// Counters for FPS display
static unsigned framesRendered;			// Counters for rendered frames

bool burnForce60Hz = false;
int nBurnCPUSpeedAdjust = 0x0100;		// CPU speed adjustment (clock * nBurnCPUSpeedAdjust / 0x0100)

// Burn Draw:
unsigned char* pBurnDraw = NULL;		// Pointer to correctly sized bitmap
int nBurnPitch = 0;						// Pitch between each line
int nBurnBpp;							// Bytes per pixel (2, 3, or 4)

int nBurnSoundRate = 0;					// sample rate of sound or zero for no sound
int nBurnSoundLen = 0;					// length in samples per frame
short* pBurnSoundOut = NULL;			// pointer to output buffer

int pcmInterpolation = 1;				// Desired interpolation level for ADPCM/PCM sound
int fmInterpolation = 0;				// Desired interpolation level for FM sound

unsigned char nBurnLayer = 0xFF;		// Can be used externally to select which layers to show
unsigned char nSpriteEnable = 0xFF;		// Can be used externally to select which layers to show

int maxPlayers;
double spAspect = -1.0;					// special game aspect

static int burnFPS = 6000;

// ---------------------------------------------------------------------------
static inline bool getDriverIndex(unsigned index, unsigned& id)
{
	if (index != ~0U && index >= burnDrvCount)
		return false;

	id = burnDrvSelect;
	if (index != ~0U) {
		id = index;
	}

	if (id == ~0U) {
		return false;
	}
	return true;
}

// ---------------------------------------------------------------------------
extern "C" int BurnLibInit()
{
	BurnLibExit();

	// count available drivers
	for (burnDrvCount = 0; drivers[burnDrvCount]; burnDrvCount++);

	cmc_4p_Precalc();

	return 0;
}

extern "C" int BurnLibExit()
{
	burnDrvCount = 0;
	return 0;
}

unsigned BurnGetDrvCount()
{
	return burnDrvCount;
}

unsigned BurnGetDrvSelect()
{
	return burnDrvSelect;
}
void BurnSetDrvSelect(unsigned select)
{
	burnDrvSelect = select;
}

// ---------------------------------------------------------------------------
// Static functions which forward to each driver's data and functions

int BurnStateMAMEScan(int action, int* min);
void BurnStateExit();
int BurnStateInit();

// get archive name, modified by regret
int BurnGetArchiveName(char** name, unsigned i, bool ext, unsigned type, unsigned index)
{
	if (name == NULL) {
		return 1;
	}

	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	const char* gameName = NULL;

	if (i == 0) {
		gameName = drivers[id]->shortName;
	} else {
		unsigned j = drivers[id]->boardRom ? 1 : 0;

		// Try BIOS/board ROMs first
		if (i == 1 && j == 1) {								// There is a BIOS/board ROM
			gameName = drivers[id]->boardRom;
		}

		if (gameName == NULL) {
			// Go through the list to seek out the parent
			while (j < i) {
				const char* parentName = drivers[id]->parentName;
				gameName = NULL;

				if (parentName == NULL) {					// No parent
					break;
				}

				for (unsigned select = 0; select < burnDrvCount; select++) {
					// Found parent
					if (strcmp(parentName, drivers[select]->shortName) == 0) {
						gameName = drivers[select]->shortName;
						break;
					}
				}

				j++;
			}
		}
	}

	if (gameName == NULL) {
		*name = NULL;
		return 1;
	}

	static char filename[256];
	strcpy(filename, gameName);

	// add extension
	if (ext) {
		if (type == 0) {			// zip
			strcat(filename, ".zip");
		}
		else if (type == 1) {		// 7z
			strcat(filename, ".7z");
		}
	}

	*name = filename;

	return 0;
}

// get driver index by game name, added by regret
extern "C" unsigned int BurnDrvGetIndexByNameA(const char* name)
{
	for (unsigned i = 0; drivers[i]; i++) {
		if (_stricmp(BurnDrvGetTextA(DRV_NAME, i), name) == 0) {
			return i;
		}
	}
	return ~0U;
}

// Get the ASCII text fields for the driver in ASCII format;
extern "C" const char* BurnDrvGetTextA(unsigned type, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return NULL;

	switch (type) {
		case DRV_NAME:
			return drivers[id]->shortName;
		case DRV_DATE:
			return drivers[id]->data;
		case DRV_FULLNAME:
			return drivers[id]->fullName;
		case DRV_COMMENT:
			return drivers[id]->comment;
		case DRV_MANUFACTURER:
			return drivers[id]->manufacturer;
		case DRV_SYSTEM:
			return drivers[id]->system;
		case DRV_PARENT:
			return drivers[id]->parentName;
		case DRV_BOARDROM:
			return drivers[id]->boardRom;
		default:
			return NULL;
	}
}

extern "C" int BurnDrvGetRomInfo(struct BurnRomInfo* pri, unsigned i, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	// Forward to drivers function
	return drivers[id]->getRomInfo(pri, i);
}

extern "C" int BurnDrvGetRomName(char** name, unsigned i, int aka, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	// Forward to drivers function
	return drivers[id]->getRomName(name, i, aka);
}

extern "C" int BurnDrvGetInputInfo(struct BurnInputInfo* pii, unsigned i, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	// Forward to drivers function
	return drivers[id]->getInputInfo(pii, i);
}

extern "C" int BurnDrvGetDIPInfo(struct BurnDIPInfo* pdi, unsigned i, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	// Forward to drivers function
	if (drivers[id]->getDIPInfo) {
		return drivers[id]->getDIPInfo(pdi, i);
	}
	return 1; // Fail automatically
}

// Get the screen size
extern "C" int BurnDrvGetVisibleSize(unsigned& width, unsigned& height, bool revert, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	if (revert && drivers[id]->flags & BDF_ORIENTATION_VERTICAL) {
		width = drivers[id]->height;
		height = drivers[id]->width;
		return 0;
	}

	width = drivers[id]->width;
	height = drivers[id]->height;
	return 0;
}

extern "C" int BurnDrvGetVisibleOffs(int& left, int& top)
{
	left = 0;
	top = 0;
	return 0;
}

// Get screen aspect ratio, modified by regret
extern "C" int BurnDrvGetAspect(double& aspect, bool swap, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	if (spAspect > 0) {
		aspect = spAspect;
		return 0;
	}

	aspect = (double)4 / (double)3;
	if (!swap) {
		return 0;
	}

	if (drivers[id]->flags & BDF_ORIENTATION_VERTICAL) {
		aspect = (double)3 / (double)4;
	}
	return 0;
}

// Get the hardware code
extern "C" int BurnDrvGetHardwareCode(unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 0;

	return drivers[id]->hardware;
}

// Get flags, including BDF_GAME_WORKING flag
extern "C" int BurnDrvGetFlags(unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 0;

	return drivers[id]->flags;
}

// Return max. number of players
extern "C" int BurnDrvGetMaxPlayers(unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 0;

	return drivers[id]->players;
}

// Set the screen size [Hack]
extern "C" int BurnDrvSetVisibleSize(unsigned width, unsigned height, unsigned index)
{
	unsigned id;
	if (!getDriverIndex(index, id))
		return 1;

	if (drivers[id]->flags & BDF_ORIENTATION_VERTICAL) {
		drivers[id]->height = width;
		drivers[id]->width = height;
	} else {
		drivers[id]->width = width;
		drivers[id]->height = height;
	}
	return 0;
}

// Set screen aspect ratio [Hack]
extern "C" int BurnDrvSetAspect(double aspect)
{
	spAspect = aspect;
	return 0;
}

// Init game emulation (loading any needed roms)
extern "C" int BurnDrvInit()
{
	if (burnDrvSelect >= burnDrvCount) {
		return 1;
	}

#if defined (FBA_DEBUG)
	{
		char text[256];
		strcpy(text, BurnDrvGetTextA(DRV_NAME));
		// Print the title
		bprintf(PRINT_IMPORTANT, "*** Starting emulation of - %s.\n", text, BurnDrvGetTextA(DRV_FULLNAME));
	}
#endif

	BurnSetRefreshRate(60.0);

	cheatInit();
	BurnStateInit();

	if (burnInterface.getIAutofire()) {
		burnInterface.getIAutofire()->init();
	}

	int retValue = drivers[burnDrvSelect]->init();	// Forward to drivers function

	maxPlayers = BurnDrvGetMaxPlayers();

#if defined (FBA_DEBUG)
	if (!retValue) {
		starttime = clock();
		framesEmulated = 0;
		framesRendered = 0;
		currentFrame = 0;
	} else {
		starttime = 0;
	}
#endif

	return retValue;
}

// Exit game emulation
extern "C" int BurnDrvExit()
{
#if defined (FBA_DEBUG)
	if (starttime) {
		clock_t endtime;
		clock_t nElapsedSecs;

		endtime = clock();
		nElapsedSecs = (endtime - starttime);
		bprintf(PRINT_IMPORTANT, " ** Emulation ended (running for %.2f seconds).\n", (float)nElapsedSecs / CLOCKS_PER_SEC);
		bprintf(PRINT_IMPORTANT, "    %.2f%% of frames rendered (%d out of a total %d).\n", (float)framesRendered / framesEmulated * 100, framesRendered, framesEmulated);
		bprintf(PRINT_IMPORTANT, "    %.2f frames per second (average).\n", (float)framesRendered / framesEmulated * burnFPS / 100);
		bprintf(PRINT_NORMAL, "\n");
	}
#endif

	cheatExit();
	BurnStateExit();

	nBurnCPUSpeedAdjust = 0x0100;

	return drivers[burnDrvSelect]->exit();		// Forward to drivers function
}

// Do one frame of game emulation
extern "C" int BurnDrvFrame()
{
	cheatApply();								// Apply cheats (if any)

	if (burnInterface.getIAutofire()) {
		burnInterface.getIAutofire()->run();
	}

	return drivers[burnDrvSelect]->frame();		// Forward to drivers function
}

// Force redraw of the screen
extern "C" int BurnDrvRedraw()
{
	if (drivers[burnDrvSelect]->redraw) {
		return drivers[burnDrvSelect]->redraw();	// Forward to drivers function
	}

	return 1;									// No funtion provide, so simply return
}

// Refresh Palette
extern "C" int BurnRecalcPal()
{
	if (burnDrvSelect < burnDrvCount) {
		unsigned char* pr = drivers[burnDrvSelect]->recalcPal;
		if (pr == NULL) return 1;
		*pr = 1;								// Signal for the driver to refresh it's palette
	}
	return 0;
}

// ---------------------------------------------------------------------------
int BurnSetProgressRange(double _range)
{
	if (burnInterface.getIProgress()) {
		return burnInterface.getIProgress()->range(_range);
	}
	return 1;
}

int BurnUpdateProgress(double progress, const char* text, bool abs)
{
	if (burnInterface.getIProgress()) {
		return burnInterface.getIProgress()->update(progress, text, abs);
	}
	return 1;
}

// ---------------------------------------------------------------------------

int GetCurrentFrame()
{
	return currentFrame;
}
void SetCurrentFrame(unsigned n)
{
	currentFrame = n;
}
int GetFramesEmulated()
{
	return framesEmulated;
}
void SetFramesEmulated(unsigned n)
{
	framesEmulated = n;
}
int GetFramesRendered()
{
	return framesRendered;
}
void SetFramesRendered(unsigned n)
{
	framesRendered = n;
}

int BurnSetRefreshRate(double refreshRate)
{
	if (!burnForce60Hz) {
		burnFPS = (int)(100.0 * refreshRate);
	}
	return 0;
}
int BurnGetRefreshRate()
{
	return burnFPS;
}

static inline int BurnClearSize(unsigned w, unsigned h)
{
	unsigned char* pl;
	unsigned y;

	w *= nBurnBpp;

	// clear the screen to zero
	for (pl = pBurnDraw, y = 0; y < h; pl += nBurnPitch, y++) {
		memset(pl, 0x00, w);
	}

	return 0;
}

int BurnClearScreen()
{
	if (drivers[burnDrvSelect]->flags & BDF_ORIENTATION_VERTICAL) {
		BurnClearSize(drivers[burnDrvSelect]->height, drivers[burnDrvSelect]->width);
	} else {
		BurnClearSize(drivers[burnDrvSelect]->width, drivers[burnDrvSelect]->height);
	}
	return 0;
}

// Application-defined colour conversion function
static unsigned int __cdecl BurnHighColFiller(int, int, int, int) { return (unsigned int)(~0); }
unsigned int (__cdecl *BurnHighCol) (int r, int g, int b, int i) = BurnHighColFiller;

// ---------------------------------------------------------------------------
// Colour-depth independant image transfer

unsigned short* pTransDraw = NULL;
static unsigned transWidth = 0, transHeight = 0;

static inline void BurnTransferCopyIn(UINT16* src, UINT8* dest, UINT32* palette)
{
	if (!transWidth || !transHeight || !pTransDraw) {
		bprintf(PRINT_NORMAL, "BurnTransferCopy called without BurnTransferInit!\n");
		return;
	}

	switch (nBurnBpp) {
		case 2: {
			for (unsigned y = 0; y < transHeight; y++, src += transWidth, dest += nBurnPitch) {
				for (unsigned x = 0; x < transWidth; x ++) {
					((UINT16*)dest)[x] = palette[src[x]];
				}
			}
			break;
		}
		case 3: {
			for (unsigned y = 0; y < transHeight; y++, src += transWidth, dest += nBurnPitch) {
				for (unsigned x = 0; x < transWidth; x++) {
					UINT32 c = palette[src[x]];
					*(dest + (x * 3) + 0) = c & 0xff;
					*(dest + (x * 3) + 1) = (c >> 8) & 0xff;
					*(dest + (x * 3) + 2) = c >> 16;

				}
			}
			break;
		}
		case 4: {
			for (unsigned y = 0; y < transHeight; y++, src += transWidth, dest += nBurnPitch) {
				for (unsigned x = 0; x < transWidth; x++) {
					((UINT32*)dest)[x] = palette[src[x]];
				}
			}
			break;
		}
	}
}

int BurnTransferCopy(UINT32* palette)
{
	BurnTransferCopyIn(pTransDraw, pBurnDraw, palette);
	return 0;
}

void BurnTransferExit()
{
	if (pTransDraw) {
		free(pTransDraw);
		pTransDraw = NULL;
	}
}

int BurnTransferInit()
{
	BurnTransferExit();

	BurnDrvGetVisibleSize(transWidth, transHeight, true);

	pTransDraw = (unsigned short*)malloc(transWidth * transHeight * sizeof(short));
	if (!pTransDraw) {
		return 1;
	}

	BurnTransferClear();
	return 0;
}

void BurnTransferClear()
{
	memset((void*)pTransDraw, 0, transWidth * transHeight * sizeof(short));
}

// ---------------------------------------------------------------------------
// Savestate support

// Application-defined callback for processing the area
static int __cdecl DefAcb (struct BurnArea* /* pba */) { return 1; }
int (__cdecl *BurnAcb) (struct BurnArea* pba) = DefAcb;

// Scan driver data
int BurnAreaScan(int action, int* min)
{
	int ret = 0;

	// Handle any MAME-style variables
	if (action & ACB_DRIVER_DATA) {
		ret = BurnStateMAMEScan(action, min);
	}

	// Forward to the driver
	if (drivers[burnDrvSelect]->scan) {
		ret |= drivers[burnDrvSelect]->scan(action, min);
	}

	return ret;
}

// ---------------------------------------------------------------------------
// Wrappers for MAME-specific function calls

#include "driver.h"

// ---------------------------------------------------------------------------
// Wrapper for MAME logerror calls

#if defined (FBA_DEBUG) && defined (MAME_USE_LOGERROR)
void logerror(char* szFormat, ...)
{
	static char szLogMessage[1024];

	va_list vaFormat;
	va_start(vaFormat, szFormat);

	_vsnprintf(szLogMessage, 1024, szFormat, vaFormat);

	va_end(vaFormat);

	bprintf(PRINT_ERROR, "%s", szLogMessage);

	return;
}
#endif

// ---------------------------------------------------------------------------
// Wrapper for MAME state_save_register_* calls

struct BurnStateEntry {
	BurnStateEntry* next;
	BurnStateEntry* prev;
	char name[256];
	void* value;
	unsigned int size;
};

static BurnStateEntry* stateEntryAnchor = NULL;
typedef void (*BurnPostloadFunction)();
static BurnPostloadFunction burnPostload[8];

static inline void BurnStateRegister(const char* module, int instance, const char* name, void* val, unsigned int size)
{
	// Allocate new node
	BurnStateEntry* newEntry = (BurnStateEntry*)malloc(sizeof(BurnStateEntry));
	if (newEntry == NULL) {
		return;
	}

	memset(newEntry, 0, sizeof(BurnStateEntry));

	// Link the new node
	newEntry->next = stateEntryAnchor;
	if (stateEntryAnchor) {
		stateEntryAnchor->prev = newEntry;
	}
	stateEntryAnchor = newEntry;

	sprintf(newEntry->name, "%s:%s %i", module, name, instance);

	newEntry->value = val;
	newEntry->size = size;
}

void BurnStateExit()
{
	if (stateEntryAnchor) {
		BurnStateEntry* currentEntry = stateEntryAnchor;
		BurnStateEntry* nextEntry;

		do {
			nextEntry = currentEntry->next;
			free(currentEntry);
		} while ((currentEntry = nextEntry) != 0);
	}

	stateEntryAnchor = NULL;

	for (int i = 0; i < 8; i++) {
		burnPostload[i] = NULL;
	}
}

int BurnStateInit()
{
	BurnStateExit();

	return 0;
}

int BurnStateMAMEScan(int action, int* min)
{
	if (action & ACB_VOLATILE) {

		if (min && *min < 0x029418) {						// Return minimum compatible version
			*min = 0x029418;
		}

		if (stateEntryAnchor) {
			struct BurnArea ba;
			BurnStateEntry* currentEntry = stateEntryAnchor;

			do {
			   	ba.Data		= currentEntry->value;
				ba.nLen		= currentEntry->size;
				ba.nAddress = 0;
				ba.szName	= currentEntry->name;
				BurnAcb(&ba);

			} while ((currentEntry = currentEntry->next) != 0);
		}

		if (action & ACB_WRITE) {
			for (int i = 0; i < 8; i++) {
				if (burnPostload[i]) {
					burnPostload[i]();
				}
			}
		}
	}

	return 0;
}

// wrapper functions
extern "C" {

void state_save_register_func_postload(void (*function)())
{
	for (int i = 0; i < 8; i++) {
		if (burnPostload[i] == NULL) {
			burnPostload[i] = function;
			break;
		}
	}
}

void state_save_register_INT8(const char* module, int instance, const char* name, INT8* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(INT8));
}
void state_save_register_UINT8(const char* module, int instance, const char* name, UINT8* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(UINT8));
}
void state_save_register_INT16(const char* module, int instance, const char* name, INT16* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(INT16));
}
void state_save_register_UINT16(const char* module, int instance, const char* name, UINT16* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(UINT16));
}
void state_save_register_INT32(const char* module, int instance, const char* name, INT32* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(INT32));
}
void state_save_register_UINT32(const char* module, int instance, const char* name, UINT32* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(UINT32));
}
void state_save_register_int(const char* module, int instance, const char* name, int* val)
{
	BurnStateRegister(module, instance, name, (void*)val, sizeof(int));
}
void state_save_register_float(const char* module, int instance, const char* name, float* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(float));
}
void state_save_register_double(const char* module, int instance, const char* name, double* val, unsigned int size)
{
	BurnStateRegister(module, instance, name, (void*)val, size * sizeof(double));
}

}
